﻿using System;
using System.Collections.Generic;
using Asteroids.Entities.Components;

namespace Asteroids.Entities
{
    /// <summary>
    /// A component container that represents an object that exists within the simulation
    /// </summary>
    public sealed class Entity
    {
        private int _id;
        private bool _enabled;
        private bool _deleted;
        private IDictionary<Type, Component> _components;
        private EntityManager _entityManager;

        public event Action<Entity, Component> ComponentAdded;
        public event Action<Entity, Component> ComponentRemoved;
        public event Action<Entity> ActiveStateModified;

        /// <summary>
        /// Initializes a new instance of <see cref="Xenon.Game.Entities.Entity"/>
        /// </summary>
        /// <param name="id"></param>
        public Entity(int id, EntityManager manager)
        {
            _id = id;
            _components = new Dictionary<Type, Component>();
            _entityManager = manager;
            Reset();
        }

        /// <summary>
        /// Adds a new <see cref="Xenon.Game.Entites.Components.Component"/>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="component"></param>
        public void AddComponent<T>(T component) where T : Component
        {
            Type type = typeof(T);

            if (Contains<T>())
                throw new InvalidOperationException("The specified type already exists within this component set");
            else
                _components.Add(type, component);

            if (ComponentAdded != null)
                ComponentAdded(this, component);
        }

        /// <summary>
        /// Removes a <see cref="Xenon.Game.Entites.Components.Component"/>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T RemoveComponent<T>() where T : Component
        {
            Type type = typeof(T);
            T removedComponent = null;

            if (Contains<T>())
            {
                _components.Remove(type);
                removedComponent = (_components[type] as T);
            }
            else
                throw new InvalidOperationException("The component of the specified type does not exist");

            if (ComponentRemoved != null)
                ComponentRemoved(this, removedComponent);

            return removedComponent;
        }

        /// <summary>
        /// Determines if a <see cref="Xenon.Game.Entities.Components.Component"/> is stored in this object
        /// </summary>
        /// <returns></returns>
        public bool Contains<T>() where T : Component
        { return _components.ContainsKey(typeof(T)); }

        /// <summary>
        /// Clears all of the <see cref="Xenon.Game.Entities.Components.Component"/>s from this object
        /// </summary>
        public void Clear()
        { _components.Clear(); }

        /// <summary>
        /// Marks this entity for removal
        /// </summary>
        public void Delete()
        {
            _deleted = true;
            _enabled = false;
        }

        /// <summary>
        /// Resets this instance to its initial state
        /// </summary>
        public void Reset()
        {
            _enabled = false;
            _deleted = false;

            foreach (var component in _components)
                component.Value.Reset();
        }

        /// <summary>
        /// Queries this entity for a list of added <see cref="Xenon.Game.Entities.Component"/>
        /// </summary>
        /// <param name="componentTypes"></param>
        /// <returns></returns>
        public ReadOnlyList<Component> QueryComponents(Type[] componentTypes)
        {
            ReadOnlyList<Component> components = default(ReadOnlyList<Component>);

            // if they passed in a null list, return an empty result
            if (componentTypes == null || componentTypes.Length == 0)
                components = ReadOnlyList<Component>.Empty;
            else
            {
                // this is the base class for components
                // we put this here because we'll be doing some comparisons from within the loop
                Type componentType = typeof(Component);
                List<Component> componentsList = new List<Component>();

                // for every type passed to us..
                for (int typeIndex = 0; typeIndex < componentTypes.Length; typeIndex++)
                {
                    // we make sure that it derives from Component
                    Type t = componentTypes[typeIndex];
                    if (t.IsAssignableFrom(componentType))
                        if (_components.ContainsKey(t))
                            componentsList.Add(_components[t]);
                        else
                            componentsList.Add(null);

                    // we throw an exception if one of the types is not derived from component
                    else
                        throw new ArgumentException("componentTypes", "One or more of the types passed in is not valid. " + t.ToString());
                }

                components = new ReadOnlyList<Component>(componentsList);
            }

            return components;
        }

        /// <summary>
        /// Queries for a specific component in this <see cref="Xenon.Game.Entities.Entity"/>, returning null if none are found
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T QueryComponent<T>() where T : Component
        {
            Type componentType = typeof(T);
            T component = null;

            if(Contains<T>())
                component = _components[componentType] as T;

            return component;
        }

        public override bool Equals(object obj)
        {
            bool equal = false;

            if (obj is Entity)
            {
                Entity otherEntity = obj as Entity;
                equal = (otherEntity.GetHashCode() == GetHashCode());
            }

            return equal;
        }

        public override int GetHashCode()
        { return _id * _components.GetHashCode(); }

        /// <summary>
        /// Determines if this <see cref="Xenon.Game.Entities.Entity"/> is enabled
        /// </summary>
        public bool Enabled
        {
            get { return _enabled || _deleted; }
            set 
            {
                if (_enabled != value)
                {
                    _enabled = value;

                    if (ActiveStateModified != null)
                        ActiveStateModified(this);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public bool Deleted
        { get { return _deleted; } }

        /// <summary>
        /// The unique ID for this <see cref="Xenon.Game.Entities.Entity"/>
        /// </summary>
        public int ID
        { get { return _id; } }

        /// <summary>
        /// The 
        /// </summary>
        public EntityManager EntityManager
        { get { return _entityManager; } }
    }
}
